/*
 * UAE - The Un*x Amiga Emulator
 *
 * Input record/playback
 *
 * Copyright 2010 Toni Wilen
 *
 */

#include "sysconfig.h"
#include "inputrecord.h"
#include "posixemu.h"
#include "win32gui.h"
#include "custom.h"
#include "disk.h"
#include "zfile.h"
#include "savestate.h"
#include "uae.h"
#include "win32.h"
#include "filesys.h"
#include "newcpu.h"

#define INPUTRECORD_DEBUG 1
#define ENABLE_DEBUGGER 1

#define HEADERSIZE 12

int inputrecord_debug = 3;

extern int inputdevice_logging;

#define INPREC_BUFFER_SIZE 10000

static byte* inprec_buffer, * inprec_p;
static struct zfile* inprec_zf;
static int inprec_size;
int input_record = 0;
int input_play = 0;
static byte* inprec_plast, * inprec_plastptr;
static int header_end, header_end2;
static int replaypos;
static int lasthsync, endhsync;
static TCHAR inprec_path[MAX_DPATH];
static uint seed;
static uint lastcycle;
static uint cycleoffset;

static uint pcs[16];
static uint pcs2[16];
extern void activate_debugger();
static int warned;

extern void refreshtitle();

static void setlasthsync()
{
    if (lasthsync / current_maxvpos() != hsync_counter / current_maxvpos())
    {
        lasthsync = hsync_counter;
        refreshtitle();
    }
}

static void flush()
{
    if (inprec_p > inprec_buffer)
    {
        zfile_fwrite(inprec_buffer, inprec_p - inprec_buffer, 1, inprec_zf);
        inprec_p = inprec_buffer;
    }
}

static void inprec_ru8(byte v)
{
    if (!input_record || !inprec_zf)
        return;
    *inprec_p++ = v;
}
static void inprec_ru16(ushort v)
{
    if (!input_record || !inprec_zf)
        return;
    inprec_ru8((byte)(v >> 8));
    inprec_ru8((byte)v);
}
void inprec_ru32(uint v)
{
    if (!input_record || !inprec_zf)
        return;
    inprec_ru16((ushort)(v >> 16));
    inprec_ru16((ushort)v);
}
static void inprec_rstr(const TCHAR* src)
{
    if (!input_record || !inprec_zf)
        return;
    char* s = Unicode::uutf8(src);
    char* ss = s;
    while (*s)
    {
        inprec_ru8(*s);
        s++;
    }
    inprec_ru8(0);
    free(ss);
}

static bool inprec_rstart(byte type)
{
    if (!input_record || !inprec_zf || input_record == INPREC_RECORD_PLAYING)
        return false;
    lastcycle = get_cycles();
    int mvp = current_maxvpos();
    if ((type != INPREC_DEBUG && type != INPREC_DEBUG2 && type != INPREC_CIADEBUG) || (0 && vsync_counter >= 49 && vsync_counter <= 51))
        Logger::Write(L"INPREC: %010d/%03d: %d (%d/%d) %08x\n", hsync_counter, current_hpos(), type, hsync_counter % mvp, mvp, lastcycle);
    inprec_plast = inprec_p;
    inprec_ru8(type);
    inprec_ru16(0xffff);
    inprec_ru32(hsync_counter);
    inprec_ru8(current_hpos());
    inprec_ru32(lastcycle);
    return true;
}

static void inprec_rend()
{
    if (!input_record || !inprec_zf)
        return;
    int size = inprec_p - inprec_plast;
    inprec_plast[1] = size >> 8;
    inprec_plast[2] = size >> 0;
    flush();
    endhsync = hsync_counter;
    setlasthsync();
}

static bool inprec_realtime(bool stopstart)
{
    if (input_record == INPREC_RECORD_RERECORD)
        gui_message(L"INPREC error");
    Logger::Write(L"INPREC: play -> record\n");
    input_record = INPREC_RECORD_RERECORD;
    input_play = 0;
    int offset = inprec_p - inprec_buffer;
    zfile_fseek(inprec_zf, offset, SEEK_SET);
    zfile_truncate(inprec_zf, offset);
    free(inprec_buffer);
    inprec_size = INPREC_BUFFER_SIZE;
    inprec_buffer = inprec_p = xmalloc(byte, inprec_size);
    clear_inputstate();
    return true;
}

static int inprec_pstart(byte type)
{
    byte* p = inprec_p;
    uint hc = hsync_counter;
    byte hpos = current_hpos();
    uint cycles = get_cycles();
    static byte* lastp;
    uint hc_orig, hc2_orig;
    int mvp = current_maxvpos();

    if (!input_play || !inprec_zf)
        return 0;
    if (savestate_state || hsync_counter > 0xffff0000)
        return 0;
    if (p == inprec_buffer + inprec_size)
    {
        Logger::Write(L"INPREC: STOP\n");
        if (input_play == INPREC_PLAY_RERECORD)
        {
            input_play = 0;
            inprec_realtime(true);
        }
        else
        {
            inprec_close(true);
        }
        return 0;
    }
    else if (p > inprec_buffer + inprec_size)
    {
        Logger::Write(L"INPREC: buffer error\n");
        gui_message(L"INPREC error");
    }
    if (p[0] == INPREC_END)
    {
        inprec_close(true);
        return 0;
    }
    else if (p[0] == INPREC_QUIT)
    {
        inprec_close(true);
        uae_quit();
        return 0;
    }
    hc_orig = hc;
    for (;;)
    {
        uint type2 = p[0];
        uint hc2 = (p[3] << 24) | (p[4] << 16) | (p[5] << 8) | p[6];
        uint hpos2 = p[7];
        uint cycles2 = (p[8] << 24) | (p[9] << 16) | (p[10] << 8) | p[11];

        if (p >= inprec_buffer + inprec_size)
            break;
        #if 0
        if (p > lastp)
        {
            Logger::Write(L"INPREC: Next %010d/%03d, %010d/%03d (%d/%d): %d (%d)\n",
                hc2, hpos2, hc, hpos, hc2 - hc, hpos2 - hpos, p[5 + 1], p[5]);
            lastp = p;
        }
        #endif
        hc2_orig = hc2;
        if (type2 == type && hc > hc2)
        {
            Logger::Write(L"INPREC: %010d/%03d > %010d/%03d: %d missed!\n", hc, hpos, hc2, hpos2, p[0]);
            #if ENABLE_DEBUGGER == 0
            gui_message(L"INPREC missed error");
            #else
            activate_debugger();
            #endif
            lastcycle = cycles;
            inprec_plast = p;
            inprec_plastptr = p + 12;
            setlasthsync();
            return 1;
        }
        if (hc2 != hc)
        {
            lastp = p;
            break;
        }
        if (type2 == type)
        {
            if (type != INPREC_DEBUG && type != INPREC_DEBUG2 && type != INPREC_CIADEBUG && cycles != cycles2)
                Logger::Write(L"INPREC: %010d/%03d: %d (%d/%d) (%d/%d) %08X/%08X\n", hc, hpos, type, hc % mvp, mvp, hc_orig - hc2_orig, hpos - hpos2, cycles, cycles2);
            if (cycles != cycles2 + cycleoffset)
            {
                if (warned > 0)
                {
                    warned--;
                    for (int i = 0; i < 7; i++)
                        Logger::Write(L"%08x (%08x) ", pcs[i], pcs2[i]);
                    Logger::Write(L"\n");
                }
                cycleoffset = cycles - cycles2;
                #if ENABLE_DEBUGGER == 0
                gui_message(L"INPREC OFFSET=%d\n", (int)cycleoffset / CYCLE_UNIT);
                #else
                activate_debugger();
                #endif
            }
            lastcycle = cycles;
            inprec_plast = p;
            inprec_plastptr = p + 12;
            setlasthsync();
            return 1;
        }
        if (type2 == INPREC_END || type2 == INPREC_QUIT)
            break;
        p += (p[1] << 8) | (p[2] << 0);
    }
    inprec_plast = nullptr;
    return 0;
}

static void inprec_pend()
{
    byte* p = inprec_p;
    uint hc = hsync_counter;
    // uint hpos = current_hpos();

    if (!input_play || !inprec_zf)
        return;
    if (!inprec_plast)
        return;
    inprec_plast[0] |= 0x80;
    inprec_plast = nullptr;
    inprec_plastptr = nullptr;
    for (;;)
    {
        uint hc2 = (p[3] << 24) | (p[4] << 16) | (p[5] << 8) | p[6];
        // uint hpos2 = p[7];
        if (hc2 != hc)
            break;
        if ((p[0] & 0x80) == 0)
            return;
        p += (p[1] << 8) | (p[2] << 0);
        inprec_p = p;
    }
}

static byte inprec_pu8()
{
    return *inprec_plastptr++;
}
static ushort inprec_pu16()
{
    ushort v = inprec_pu8() << 8;
    v |= inprec_pu8();
    return v;
}
static short inprec_ps16()
{
    ushort v = inprec_pu8() << 8;
    v |= inprec_pu8();
    return (short)v;
}
static uint inprec_pu32()
{
    uint v = inprec_pu16() << 16;
    v |= inprec_pu16();
    return v;
}
static int inprec_pstr(TCHAR* dst)
{
    char tmp[MAX_DPATH];
    char* s;
    int len = 0;

    *dst = 0;
    s = tmp;
    for (;;)
    {
        char v = inprec_pu8();
        *s++ = v;
        if (!v)
            break;
        len++;
    }
    if (tmp[0])
    {
        TCHAR* d = Unicode::utf8u(tmp);
        _tcscpy(dst, d);
        free(d);
    }
    return len;
}

static void findlast()
{
    uint hsync = 0;
    byte* p = inprec_p;
    while (p < inprec_buffer + inprec_size)
    {
        hsync = (p[3] << 24) | (p[4] << 16) | (p[5] << 8) | p[6];
        ushort len = (p[1] << 8) | (p[2] << 0);
        p += len;
    }
    endhsync = hsync;
}

int inprec_open(const TCHAR* fname, const TCHAR* statefilename)
{
    int i;

    inprec_close(false);
    if (fname == nullptr)
        inprec_zf = zfile_fopen_empty(nullptr, L"inp");
    else
        inprec_zf = zfile_fopen(fname, input_record ? L"wb" : L"rb", ZFD_NORMAL);
    if (inprec_zf == nullptr)
        return 0;

    g_curr_conf.cs_rtc = g_changed_conf.cs_rtc = 0;

    inprec_path[0] = 0;
    if (fname)
        getpathpart(inprec_path, sizeof inprec_path / sizeof(TCHAR), fname);
    seed = (uint)time(0);
    inprec_size = INPREC_BUFFER_SIZE;
    lasthsync = 0;
    endhsync = 0;
    warned = 10;
    cycleoffset = 0;
    header_end2 = 0;
    if (input_play)
    {
        uint id;
        zfile_fseek(inprec_zf, 0, SEEK_END);
        inprec_size = zfile_ftell(inprec_zf);
        zfile_fseek(inprec_zf, 0, SEEK_SET);
        inprec_buffer = inprec_p = xmalloc(byte, inprec_size);
        zfile_fread(inprec_buffer, inprec_size, 1, inprec_zf);
        inprec_plastptr = inprec_buffer;
        id = inprec_pu32();
        if (id != 'UAE\0')
        {
            inprec_close(true);
            return 0;
        }
        int v = inprec_pu8();
        if (v != 2)
        {
            inprec_close(true);
            return 0;
        }
        inprec_pu8();
        inprec_pu8();
        inprec_pu8();
        seed = inprec_pu32();
        seed = uaesrand(seed);
        vsync_counter = inprec_pu32();
        hsync_counter = inprec_pu32();
        i = inprec_pu32();
        while (i-- > 0)
            inprec_pu8();
        header_end = inprec_plastptr - inprec_buffer;
        inprec_pstr(savestate_fname);
        if (savestate_fname[0])
        {
            savestate_state = STATE_RESTORE;
            for (;;)
            {
                TCHAR tmp[MAX_DPATH];
                _tcscpy(tmp, fname);
                _tcscat(tmp, L".uss");
                if (zfile_exists(tmp))
                {
                    _tcscpy(savestate_fname, tmp);
                    break;
                }
                if (zfile_exists(savestate_fname))
                    break;
                TCHAR* p = _tcsrchr(savestate_fname, '\\');
                if (!p)
                    p = _tcsrchr(savestate_fname, '/');
                if (!p)
                    p = savestate_fname;
                else
                    p++;
                if (zfile_exists(p))
                {
                    _tcscpy(savestate_fname, p);
                    break;
                }
                fetch_statefilepath(tmp, sizeof tmp / sizeof(TCHAR));
                _tcscat(tmp, p);
                if (zfile_exists(tmp))
                {
                    _tcscpy(savestate_fname, tmp);
                    break;
                }
                fetch_inputfilepath(tmp, sizeof tmp / sizeof(TCHAR));
                _tcscat(tmp, p);
                if (zfile_exists(tmp))
                {
                    _tcscpy(savestate_fname, tmp);
                    break;
                }
                Logger::Write(L"Failed to open linked statefile '%s'\n", savestate_fname);
                savestate_fname[0] = 0;
                savestate_state = 0;
                break;
            }
        }
        inprec_p = inprec_plastptr;
        header_end2 = inprec_plastptr - inprec_buffer;
        findlast();
    }
    else if (input_record)
    {
        seed = uaesrand(seed);
        inprec_buffer = inprec_p = xmalloc(byte, inprec_size);
        inprec_ru32('UAE\0');
        inprec_ru8(2);
        inprec_ru8(UAEMAJOR);
        inprec_ru8(UAEMINOR);
        inprec_ru8(UAESUBREV);
        inprec_ru32(seed);
        inprec_ru32(vsync_counter);
        inprec_ru32(hsync_counter);
        inprec_ru32(0);  // extra header size
        flush();
        header_end2 = header_end = zfile_ftell(inprec_zf);
    }
    else
    {
        input_record = input_play = 0;
        return 0;
    }
    if (inputrecord_debug)
    {
        if (Logging::FDD_DEBUG < 1)
            Logging::FDD_DEBUG = 1 | 2;
    }
    Logger::Write(L"inprec initialized '%s', play=%d rec=%d\n", fname ? fname : L"<internal>", input_play, input_record);
    refreshtitle();
    return 1;
}

void inprec_startup()
{
    uaesrand(seed);
}

bool inprec_prepare_record(const TCHAR* statefilename)
{
    TCHAR state[MAX_DPATH];
    int mode = statefilename ? 2 : 1;
    state[0] = 0;
    if (statefilename)
        _tcscpy(state, statefilename);
    if (hsync_counter > 0 && savestate_state == 0)
    {
        WCHAR temp[MAX_DPATH];
        wcscpy(temp, g_changed_conf.inprecfile);

        TCHAR* s = _tcsrchr(temp, L'\\');
        if (!s)
            s = _tcsrchr(temp, '/');
        if (s)
        {
            fetch_statefilepath(state, sizeof state / sizeof(TCHAR));
            _tcscat(state, s + 1);
        }
        else
        {
            _tcscpy(state, temp);
        }
        _tcscat(state, L".uss");
        savestate_initsave(state, 1, 1, true);
        save_state(state, L"input recording test");
        mode = 2;
    }
    input_record = INPREC_RECORD_NORMAL;
    inprec_open(g_changed_conf.inprecfile, state);
    g_changed_conf.inprecfile = g_curr_conf.inprecfile = "";
    return true;
}

void inprec_close(bool clear)
{
    if (clear)
        input_play = input_record = 0;
    if (!inprec_zf)
        return;
    if (inprec_buffer && input_record)
    {
        if (inprec_rstart(INPREC_END))
            inprec_rend();
    }
    zfile_fclose(inprec_zf);
    inprec_zf = nullptr;
    free(inprec_buffer);
    inprec_buffer = nullptr;
    input_play = input_record = 0;
    Logger::Write(L"inprec finished\n");
    refreshtitle();
}

static void setwriteprotect(const TCHAR* fname, bool readonly)
{
    struct _stat64 st;
    int mode, oldmode;
    if (stat(fname, &st))
        return;
    oldmode = mode = st.st_mode;
    mode &= ~FILEFLAG_WRITE;
    if (!readonly)
        mode |= FILEFLAG_WRITE;
    if (mode != oldmode)
        chmod(fname, mode);
}

void inprec_playdiskchange()
{
    if (!input_play)
        return;
    while (inprec_pstart(INPREC_DISKREMOVE))
    {
        int drv = inprec_pu8();
        inprec_pend();
        Logger::Write(L"INPREC: disk eject drive %d\n", drv);
        disk_eject(drv);
    }
    while (inprec_pstart(INPREC_DISKINSERT))
    {
        int drv = inprec_pu8();
        bool wp = inprec_pu8() != 0;
        TCHAR tmp[MAX_DPATH], tmp2[MAX_DPATH];
        inprec_pstr(tmp);
        _tcscpy(tmp2, tmp);
        if (!zfile_exists(tmp))
        {
            TCHAR tmp3[MAX_DPATH];
            _tcscpy(tmp3, inprec_path);
            _tcscat(tmp3, tmp);
            _tcscpy(tmp, tmp3);
        }
        if (!zfile_exists(tmp))
        {
            gui_message(L"INPREC: Disk image\n'%s'\nnot found!\n", tmp2);
        }
        g_curr_conf.floppies[drv].df = tmp;
        g_changed_conf.floppies[drv].df = tmp;
        setwriteprotect(tmp, wp);
        disk_insert_force(drv, tmp, wp);
        Logger::Write(L"INPREC: disk insert drive %d '%s'\n", drv, tmp);
        inprec_pend();
    }
}

bool inprec_playevent(int* nr, int* state, int* max, int* autofire)
{
    if (inprec_pstart(INPREC_EVENT))
    {
        *nr = inprec_ps16();
        *state = inprec_ps16();
        *max = inprec_pu16();
        *autofire = inprec_ps16() & 1;
        inprec_pend();
        return true;
    }
    return false;
}

void inprec_recorddebug_cia(uint v1, uint v2, uint v3)
{
    #if INPUTRECORD_DEBUG > 0
    if (inprec_rstart(INPREC_CIADEBUG))
    {
        inprec_ru32(v1);
        inprec_ru32(v2);
        inprec_ru32(v3);
        inprec_rend();
    }
    #endif
}
void inprec_playdebug_cia(uint v1, uint v2, uint v3)
{
    #if INPUTRECORD_DEBUG > 0
    // int err = 0;
    if (inprec_pstart(INPREC_CIADEBUG))
    {
        uint vv1 = inprec_pu32();
        uint vv2 = inprec_pu32();
        uint vv3 = inprec_pu32();
        if (vv1 != v1 || vv2 != v2 || vv3 != v3)
            Logger::Write(L"CIA SYNC ERROR %08x,%08x %08x,%08x %08x,%08x\n", vv1, v1, vv2, v2, vv3, v3);
        inprec_pend();
    }
    #endif
}

void inprec_recorddebug_cpu(int mode)
{
    #if INPUTRECORD_DEBUG > 0
    if (inprec_rstart(INPREC_DEBUG2))
    {
        inprec_ru32(m68k_getpc());
        inprec_ru32(get_cycles() | mode);
        inprec_rend();
    }
    #endif
}
void inprec_playdebug_cpu(int mode)
{
    #if INPUTRECORD_DEBUG > 0
    int err = 0;
    if (inprec_pstart(INPREC_DEBUG2))
    {
        uint pc1 = m68k_getpc();
        uint pc2 = inprec_pu32();
        uint v1 = get_cycles() | mode;
        uint v2 = inprec_pu32();
        if (pc1 != pc2)
        {
            if (warned > 0)
            {
                warned--;
                Logger::Write(L"SYNC ERROR2 PC %08x != %08x\n", pc1, pc2);
                for (int i = 0; i < 15; i++)
                    Logger::Write(L"%08x ", pcs[i]);
                Logger::Write(L"\n");
            }
            err = 1;
        }
        else
        {
            memmove(pcs + 1, pcs, 15 * 4);
            pcs[0] = pc1;
            memmove(pcs2 + 1, pcs2, 15 * 4);
            pcs2[0] = get_cycles();
        }
        if (v1 != v2)
        {
            if (warned > 0)
            {
                warned--;
                Logger::Write(L"SYNC ERROR2 %08x != %08x\n", v1, v2);
                for (int i = 0; i < 15; i++)
                    Logger::Write(L"%08x ", pcs[i]);
                Logger::Write(L"\n");
            }
            err = 1;
        }
        inprec_pend();
    }
    else if (input_play > 0)
    {
        if (warned > 0)
        {
            warned--;
            Logger::Write(L"SYNC ERROR2 debug event missing!?\n");
        }
    }
    #endif
}

void inprec_recorddebug(uint val)
{
    #if INPUTRECORD_DEBUG > 0
    if (inprec_rstart(INPREC_DEBUG))
    {
        inprec_ru32(uaerandgetseed());
        inprec_ru32(val);
        inprec_rend();
    }
    #endif
}
void inprec_playdebug(uint val)
{
    #if INPUTRECORD_DEBUG > 0
    extern void activate_debugger();
    static uint pcs[16];
    int err = 0;
    if (inprec_pstart(INPREC_DEBUG))
    {
        uint seed1 = uaerandgetseed();
        uint seed2 = inprec_pu32();
        if (seed1 != seed2)
        {
            Logger::Write(L"SYNC ERROR seed %08x != %08x\n", seed1, seed2);
            err = 1;
        }
        uint val2 = inprec_pu32();
        if (val != val2)
        {
            Logger::Write(L"SYNC ERROR val %08x != %08x\n", val, val2);
            err = 1;
        }
        inprec_pend();
    }
    else if (input_play > 0)
    {
        gui_message(L"SYNC ERROR debug event missing!?\n");
    }
    #endif
}

void inprec_recordevent(int nr, int state, int max, int autofire)
{
    if (savestate_state)
        return;
    if (input_record < INPREC_RECORD_NORMAL)
        return;
    if (inprec_rstart(INPREC_EVENT))
    {
        inprec_ru16(nr);
        inprec_ru16(state);
        inprec_ru16(max);
        inprec_ru16(autofire ? 1 : 0);
        inprec_rend();
        if (input_record == INPREC_RECORD_NORMAL)
            input_record = INPREC_RECORD_RERECORD;
    }
}

void inprec_recorddiskchange(int nr, const TCHAR* fname, bool writeprotected)
{
    if (savestate_state)
        return;
    if (input_record < INPREC_RECORD_NORMAL)
        return;
    if (fname && fname[0])
    {
        if (inprec_rstart(INPREC_DISKINSERT))
        {
            inprec_ru8(nr);
            inprec_ru8(writeprotected ? 1 : 0);
            inprec_rstr(fname);
            Logger::Write(L"INPREC: disk insert %d '%s'\n", nr, fname);
            inprec_rend();
        }
    }
    else
    {
        if (inprec_rstart(INPREC_DISKREMOVE))
        {
            inprec_ru8(nr);
            Logger::Write(L"INPREC: disk eject %d\n", nr);
            inprec_rend();
        }
    }
}

int inprec_getposition()
{
    int pos = -1;
    if (input_play == INPREC_PLAY_RERECORD)
    {
        pos = inprec_p - inprec_buffer;
    }
    else if (input_record)
    {
        pos = zfile_ftell(inprec_zf);
    }
    Logger::Write(L"INPREC: getpos=%d cycles=%08X\n", pos, lastcycle);
    if (pos < 0)
    {
        Logger::Write(L"INPREC: getpos failure\n");
        gui_message(L"INPREC error");
    }
    return pos;
}

//  normal play to re-record
void inprec_playtorecord()
{
    Logger::Write(L"INPREC: PLAY to RE-RECORD\n");
    replaypos = 0;
    findlast();
    input_play = INPREC_PLAY_RERECORD;
    input_record = INPREC_RECORD_PLAYING;
    zfile_fclose(inprec_zf);
    inprec_zf = zfile_fopen_empty(nullptr, L"inp");
    zfile_fwrite(inprec_buffer, header_end2, 1, inprec_zf);
    byte* p = inprec_buffer + header_end2;
    byte* end = inprec_buffer + inprec_size;
    while (p < end)
    {
        int len = (p[1] << 8) | (p[2] << 0);
        p[0] &= ~0x80;
        p += len;
    }
    zfile_fwrite(inprec_buffer + header_end2, inprec_size - header_end2, 1, inprec_zf);
    inprec_realtime(false);
    savestate_capture_request();
}

void inprec_setposition(int offset, int replaycounter)
{
    if (!inprec_buffer)
        return;
    replaypos = replaycounter;
    Logger::Write(L"INPREC: setpos=%d\n", offset);
    if (offset < header_end || offset > zfile_size(inprec_zf))
    {
        Logger::Write(L"INPREC: buffer corruption. offset=%d, size=%d\n", offset, zfile_size(inprec_zf));
        gui_message(L"INPREC error");
    }
    zfile_fseek(inprec_zf, 0, SEEK_SET);
    free(inprec_buffer);
    inprec_size = zfile_size(inprec_zf);
    inprec_buffer = xmalloc(byte, inprec_size);
    zfile_fread(inprec_buffer, inprec_size, 1, inprec_zf);
    inprec_p = inprec_plastptr = inprec_buffer + offset;
    findlast();
    input_play = INPREC_PLAY_RERECORD;
    input_record = INPREC_RECORD_PLAYING;
    if (g_curr_conf.inprec_autoplay == false)
        inprec_realtime(false);
}

static void savelog(const TCHAR* path, const TCHAR* file)
{
    TCHAR tmp[MAX_DPATH];

    _tcscpy(tmp, path);
    _tcscat(tmp, file);
    _tcscat(tmp, L".log.txt");
    struct zfile* zfd = zfile_fopen(tmp, L"wb");
    if (zfd)
    {
        int loglen;
        byte* log;
        loglen = 0;
        log = save_log(TRUE, &loglen);
        if (log)
            zfile_fwrite(log, loglen, 1, zfd);
        free(log);
        loglen = 0;
        log = save_log(FALSE, &loglen);
        if (log)
            zfile_fwrite(log, loglen, 1, zfd);
        free(log);
        zfile_fclose(zfd);
        Logger::Write(L"log '%s' saved\n", tmp);
    }
}

static int savedisk(const TCHAR* path, const TCHAR* file, byte* data, byte* outdata)
{
    int len = 0;
    TCHAR* fname = Unicode::utf8u((const char*)data + 2);
    if (fname[0])
    {
        TCHAR tmp[MAX_DPATH];
        TCHAR filename[MAX_DPATH];
        filename[0] = 0;
        struct zfile* zf = zfile_fopen(fname, L"rb", ZFD_NORMAL);
        if (!zf)
        {
            _tcscpy(tmp, path);
            _tcscat(tmp, fname);
            zf = zfile_fopen(tmp, L"rb", ZFD_NORMAL);
            if (!zf)
                Logger::Write(L"failed to open '%s'\n", tmp);
        }
        if (zf)
        {
            _tcscpy(tmp, path);
            _tcscpy(filename, file);
            _tcscat(filename, L".");
            getfilepart(filename + _tcslen(filename), MAX_DPATH, zfile_getname(zf));
            _tcscat(tmp, filename);
            struct zfile* zfd = zfile_fopen(tmp, L"wb");
            if (zfd)
            {
                int size = zfile_size(zf);
                byte* data = zfile_getdata(zf, 0, size);
                zfile_fwrite(data, size, 1, zfd);
                zfile_fclose(zfd);
                free(data);
            }
            zfile_fclose(zf);
            setwriteprotect(fname, data[1] != 0);
        }
        if (filename[0])
        {
            outdata[0] = data[0];
            char* fn = Unicode::uutf8(filename);
            strcpy((char*)outdata + 2, fn);
            free(fn);
            len = 2 + strlen((char*)outdata + 2) + 1;
        }
    }
    free(fname);
    return len;
}

void inprec_save(const TCHAR* filename, const TCHAR* statefilename)
{
    TCHAR path[MAX_DPATH], file[MAX_DPATH];
    if (!inprec_buffer)
        return;
    getpathpart(path, sizeof path / sizeof(TCHAR), filename);
    getfilepart(file, sizeof file / sizeof(TCHAR), filename);
    struct zfile* zf = zfile_fopen(filename, L"wb", 0);
    if (zf)
    {
        TCHAR fn[MAX_DPATH];
        byte* data;
        data = zfile_getdata(inprec_zf, 0, header_end);
        zfile_fwrite(data, header_end, 1, zf);
        free(data);
        getfilepart(fn, MAX_DPATH, statefilename);
        char* s = Unicode::uutf8(fn);
        zfile_fwrite(s, strlen(s) + 1, 1, zf);
        int len = zfile_size(inprec_zf) - header_end2;
        data = zfile_getdata(inprec_zf, header_end2, len);
        byte* p = data;
        byte* end = data + len;
        while (p < end)
        {
            byte tmp[MAX_DPATH];
            int plen = (p[1] << 8) | (p[2] << 0);
            int wlen = plen - HEADERSIZE;
            memcpy(tmp, p + HEADERSIZE, wlen);
            if (p[0] == INPREC_DISKINSERT)
            {
                wlen = savedisk(path, file, p + HEADERSIZE, tmp);
            }
            if (wlen)
            {
                wlen += HEADERSIZE;
                p[1] = wlen >> 8;
                p[2] = wlen;
                zfile_fwrite(p, HEADERSIZE, 1, zf);
                zfile_fwrite(tmp, wlen - HEADERSIZE, 1, zf);
            }
            else
            {
                zfile_fwrite(p, plen, 1, zf);
            }
            p += plen;
        }
        free(data);
        zfile_fclose(zf);
        savelog(path, file);
        Logger::Write(L"inputfile '%s' saved\n", filename);
    }
    else
    {
        Logger::Write(L"failed to open '%s'\n", filename);
    }
}

bool inprec_realtime()
{
    if (input_record != INPREC_RECORD_PLAYING || input_play != INPREC_PLAY_RERECORD)
        return false;
    // clear_inputstate();
    return inprec_realtime(false);
}

void inprec_getstatus(TCHAR* title)
{
    TCHAR* p;
    if (!input_record && !input_play)
        return;
    _tcscat(title, L"[");
    if (input_record)
    {
        if (input_record != INPREC_RECORD_PLAYING)
            _tcscat(title, L"-REC-");
        else
            _tcscat(title, L"REPLAY");
    }
    else if (input_play)
    {
        _tcscat(title, L"PLAY-");
    }
    _tcscat(title, L" ");
    p = title + _tcslen(title);
    int mvp = current_maxvpos();
    _stprintf(p, L"%03d %02d:%02d:%02d/%02d:%02d:%02d", replaypos,
        lasthsync / (vblank_hz * mvp * 60), ((int)(lasthsync / (vblank_hz * mvp)) % 60), (lasthsync / mvp) % (int)vblank_hz,
        endhsync / (vblank_hz * mvp * 60), ((int)(endhsync / (vblank_hz * mvp)) % 60), (endhsync / mvp) % (int)vblank_hz);
    p += _tcslen(p);
    _tcscat(p, L"] ");
}